package com.xrui.xml;

import org.apache.log4j.Logger;
import org.codehaus.stax2.XMLInputFactory2;
import org.codehaus.stax2.XMLStreamReader2;
import org.xml.sax.XMLReader;
import org.xml.sax.helpers.XMLReaderFactory;

import javax.xml.stream.XMLEventReader;
import javax.xml.stream.XMLInputFactory;
import javax.xml.stream.XMLStreamConstants;
import javax.xml.stream.XMLStreamException;
import javax.xml.stream.events.Attribute;
import javax.xml.stream.events.Characters;
import javax.xml.stream.events.StartElement;
import javax.xml.stream.events.XMLEvent;
import java.io.*;
import java.util.*;

public class GenericXmlParser {
    private static final Logger LOG = Logger.getLogger(GenericXmlParser.class);

    File file;
    String endElementName;
    Map<String, Setter> map;
    List<ElementEventListener> listeners;
    Map<String, ElementEventIndicatorListener> indicatorListener;
    RecordCreator recordCreator;

    public GenericXmlParser(File file, Map<String, Setter> map, RecordCreator recordCreator) {
        this.file = file;
        this.map = map;
        this.listeners = new ArrayList<ElementEventListener>();
        this.indicatorListener = new HashMap<String, ElementEventIndicatorListener>();
        this.recordCreator = recordCreator;
    }
    public void addListener(ElementEventListener listener) {
        this.listeners.add(listener);
    }

    public void addElementIndicatorListener(String element, ElementEventIndicatorListener listener) {
        this.indicatorListener.put(element, listener);
    }

    public void setEndElement(String endElementName) {
        this.endElementName = endElementName;
    }

    public void updateListeners(Record rec) {
        if (rec != null) {
            for (ElementEventListener listener : listeners) {
                listener.recordDone(rec);
            }
        }
    }

    public void parseFileWithSax() {
        try {
            XMLReader oXReader = XMLReaderFactory.createXMLReader("org.apache.xerces.parsers.SAXParser");

            xmlparser handler = new xmlparser();

            oXReader.setFeature("http://xml.org/sax/features/namespaces", true);


            oXReader.setContentHandler(handler);


            oXReader.setDTDHandler(handler);

            oXReader.setEntityResolver(handler);

        }
        catch(Exception e){

        }
    }


    public void parseFileByStax2() throws XMLStreamException {
        XMLInputFactory2 xmlif = null;
        try {
            xmlif = (XMLInputFactory2)
                    org.codehaus.stax2.XMLInputFactory2.newInstance();
            xmlif.setProperty(
                    XMLInputFactory.
                            IS_REPLACING_ENTITY_REFERENCES,
                    Boolean.FALSE);
            xmlif.setProperty(
                    XMLInputFactory.
                            IS_SUPPORTING_EXTERNAL_ENTITIES,
                    Boolean.FALSE);
            xmlif.setProperty(
                    XMLInputFactory.
                            IS_COALESCING,
                    Boolean.TRUE);
            xmlif.configureForSpeed();
        } catch (Exception ex) {
            ex.printStackTrace();
        }
        long starttime =
                System.currentTimeMillis();
        int elementCount = 0;
        int filteredCharCount = 0;

        try {

            if (!this.file.exists()) {
                throw new IllegalStateException("File does not exist: " + file);
            }

            XMLStreamReader2 xmlr = (XMLStreamReader2) xmlif.createXMLStreamReader(this.file);
            int eventType = xmlr.getEventType();
            String curElement = "";

            Record rec = null;
            Properties attr = null;
            boolean processNestedElement = false;
            //this code can only deal with element with text or child element,
            //can't deal with mixed element with text and child element
            while (xmlr.hasNext() || processNestedElement) {
                if (!processNestedElement) {
                    eventType = xmlr.next();
                }
                processNestedElement = false;

                switch (eventType) {
                    case XMLEvent.START_ELEMENT:
                        curElement = xmlr.getPrefixedName();

                        if (curElement.equals(this.endElementName)) {
                            updateListeners(rec);
                            rec = this.recordCreator.create();

                            //System.out.println("curElement :" + curElement);
                            Setter setter = map.get(curElement);
                            if (setter != null) {
                                attr = new Properties();
                                for (int i = 0; i < xmlr.getAttributeCount(); i++) {
                                    String attrName = xmlr.getAttributeLocalName(i);
                                    String attrVal = xmlr.getAttributeValue(i);
                                    attr.setProperty(attrName, attrVal);
                                }
                                String text = null;
                                if (xmlr.hasNext()) {
                                    eventType = xmlr.next();
                                    //case1: get the text of the interested element
                                    // until meet the end of current element or meet a nested element
                                    if (eventType == XMLStreamConstants.CHARACTERS
                                            || eventType == XMLStreamConstants.CDATA
                                            || eventType == XMLStreamConstants.SPACE
                                            || eventType == XMLStreamConstants.ENTITY_REFERENCE){

                                        StringBuilder buf = new StringBuilder();
                                        while (eventType != XMLStreamConstants.END_ELEMENT) {
                                            if (eventType == XMLStreamConstants.CHARACTERS
                                                    || eventType == XMLStreamConstants.CDATA
                                                    || eventType == XMLStreamConstants.SPACE
                                                    || eventType == XMLStreamConstants.ENTITY_REFERENCE) {
                                                buf.append(xmlr.getText());
                                            } else if (eventType == XMLStreamConstants.PROCESSING_INSTRUCTION
                                                    || eventType == XMLStreamConstants.COMMENT) {
                                                // skipping
                                            } else if (eventType == XMLStreamConstants.END_DOCUMENT) {
                                                throw new XMLStreamException(
                                                        "unexpected end of document when reading element text content");
                                            } else if (eventType == XMLStreamConstants.START_ELEMENT) {
                                                processNestedElement = true;
                                                curElement = xmlr.getPrefixedName();
                                                break;
                                            } else {
                                                throw new XMLStreamException(
                                                        "Unexpected event type " + eventType);
                                            }
                                            eventType = xmlr.next();
                                        }
                                        text = buf.toString();
                                    }
                                    //case2: got a nested element
                                    else if(eventType == XMLStreamConstants.START_ELEMENT){
                                        processNestedElement = true;
                                        curElement = xmlr.getPrefixedName();
                                    }

                                }
                                setter.set(text, rec, attr);
                            }
                        }
                        else { //not interested element, do nothing

                        }
                        ElementEventIndicatorListener ilistener = this.indicatorListener.get(curElement);
                        if (ilistener != null) {
                            ilistener.elementBegin(curElement, rec);
                        }

                        break;
                    case XMLEvent.CHARACTERS:
                        break;
                    case XMLEvent.END_ELEMENT:
                        ElementEventIndicatorListener ilistener2 = this.indicatorListener.get(curElement);
                        if (ilistener2 != null) {
                            ilistener2.elementEnd(curElement, rec);
                        }
                        break;

                    case XMLEvent.END_DOCUMENT:
                    default:
                }
                if (eventType == XMLEvent.START_ELEMENT) {
                    curElement = xmlr.getPrefixedName();
                } else {
                    if (eventType ==
                            XMLEvent.CHARACTERS) {
                    }
                }
            }

            updateListeners(rec);
        } catch (XMLStreamException ex) {

            LOG.error(ex.getMessage());
            ex.printStackTrace();
            throw ex;
        } catch (Exception ex) {
            LOG.error(ex.getMessage());
            ex.printStackTrace();
            throw ex;
        }

    }

    public void parseFileByStax() throws XMLStreamException,FileNotFoundException,UnsupportedEncodingException {

        if (!this.file.exists()) {
            throw new IllegalStateException("File does not exist: " + file);
        }

        XMLInputFactory factory = XMLInputFactory.newInstance();
        factory.setProperty("javax.xml.stream.isCoalescing",Boolean.FALSE);
        factory.setProperty( XMLInputFactory.IS_REPLACING_ENTITY_REFERENCES, Boolean.FALSE);
        factory.setProperty( XMLInputFactory.IS_SUPPORTING_EXTERNAL_ENTITIES, Boolean.FALSE);

        try {

            //BufferedReader in
            //        = new BufferedReader(new InputStreamReader(new FileInputStream(file),"UTF-8"));
            BufferedInputStream in = new BufferedInputStream(new FileInputStream(file));
            XMLEventReader xmlr = factory.createXMLEventReader(in);


            XMLEvent event= null;
            String curElementName = "";

            StartElement se =  null;
            Set<String> typeSet = new HashSet<String>();
            long orgCount = 0;
            boolean firstTime = true;
            Record rec = null;
            Properties attr = null;
            boolean processNestedElement = false;
            while (xmlr.hasNext() || processNestedElement) {
                if (!processNestedElement) {
                    event = xmlr.nextEvent();
                }
                processNestedElement = false;
                String curValue = null;
                switch (event.getEventType()) {
                    case XMLEvent.START_ELEMENT:
                        se = event.asStartElement();
                        curElementName = se.getName().getLocalPart();

                        if (curElementName.equals(this.endElementName)) { //meet a new interested element, save the previous one.
                            updateListeners(rec);
                            rec = this.recordCreator.create();
                        }

                        Setter setter = map.get(curElementName);
                        if (setter != null) {
                            attr = new Properties();

                            Iterator seai = se.getAttributes();
                            while (seai.hasNext()) {
                                Attribute attribute =  (Attribute)seai.next();
                                String attrName = attribute.getName().getLocalPart();
                                String attrVal =  attribute.getValue();
                                attr.setProperty(attrName, attrVal);
                            }
                            String text = null;
                            if (xmlr.hasNext()) {
                                event = xmlr.nextEvent();
                                switch (event.getEventType()) {
                                    case XMLEvent.CHARACTERS:

                                        Characters cdata = event.asCharacters();
                                        //if(cdata.isWhiteSpace()) continue;

                                        text =  cdata.getData( );
                                        break;
                                    case XMLEvent.END_ELEMENT:
                                        break;
                                    case XMLEvent.START_ELEMENT:
                                        processNestedElement = true;
                                        curElementName = event.asStartElement().getName().getLocalPart();
                                    default:
                                        break;
                                }
                            }
                            setter.set(text, rec, attr);
                        }
                        ElementEventIndicatorListener ilistener = this.indicatorListener.get(curElementName);
                        if (ilistener != null) {
                            ilistener.elementBegin(curElementName, rec);
                        }

                        break;
                    case XMLEvent.CHARACTERS:
                        ElementEventIndicatorListener ilistener2 = this.indicatorListener.get(curElementName);
                        if (ilistener2 != null) {
                            ilistener2.elementEnd(curElementName, rec);
                        }
                        break;
                    case XMLEvent.END_ELEMENT:
                        break;
                    case XMLEvent.END_DOCUMENT:
                }
                if (event.getEventType() == XMLEvent.START_ELEMENT) {
                    curElementName = event.asStartElement().getName().getLocalPart();
                } else {
                    if (event.getEventType() ==
                            XMLEvent.CHARACTERS) {
                    }
                }
            }

            updateListeners(rec);

        } catch (Exception ex) {
            LOG.error(ex.getMessage());
            ex.printStackTrace();
            throw ex;
        }

    }

}
